* Allow or disallow account creation from blocked IP addressess on a per-block basis.
* Prevent duplicate blocks.
* Fixed the problem of expiry and unblocking erroneously affecting multiple blocks.
* Fixed confusing lack of error message when a blocked user attempts to create an account.
* Fixed inefficiency of Special:Ipblocklist in the presence of large numbers of blocks; added indexes and implemented an indexed pager.
== Major new features ==
-* None!
-
-
+* (bug 550) Allow blocks on anonymous users only.
== Changes since 1.7 ==
* (bug 6586) Regression in "unblocked" subtitle
* Don't put empty-page message into view-source when page text is blank
* (bug 6587) Remove redundant "allnonarticles" message
+* Block improvements: Allow blocks on anonymous users only. Optionally allow
+ or disallow account creation from blocked IP addresses. Prevent duplicate
+ blocks. Fixed the problem of expiry and unblocking erroneously affecting
+ multiple blocks. Fixed confusing lack of error message when a blocked user
+ attempts to create an account. Fixed inefficiency of Special:Ipblocklist in
+ the presence of large numbers of blocks; added indexes and implemented an
+ indexed pager.
== Languages updated ==
* All the functions in this class assume the object is either explicitly
* loaded or filled. It is not load-on-demand. There are no accessors.
*
- * To use delete(), you only need to fill $mAddress
* Globals used: $wgAutoblockExpiry, $wgAntiLockFlags
*
* @todo This could be used everywhere, but it isn't.
class Block
{
/* public*/ var $mAddress, $mUser, $mBy, $mReason, $mTimestamp, $mAuto, $mId, $mExpiry,
- $mRangeStart, $mRangeEnd;
+ $mRangeStart, $mRangeEnd, $mAnonOnly;
/* private */ var $mNetworkBits, $mIntegerAddr, $mForUpdate, $mFromMaster, $mByName;
const EB_KEEP_EXPIRED = 1;
const EB_RANGE_ONLY = 4;
function Block( $address = '', $user = '', $by = 0, $reason = '',
- $timestamp = '' , $auto = 0, $expiry = '' )
+ $timestamp = '' , $auto = 0, $expiry = '', $anonOnly = 0, $createAccount = 0 )
{
+ $this->mId = 0;
$this->mAddress = $address;
$this->mUser = $user;
$this->mBy = $by;
$this->mReason = $reason;
$this->mTimestamp = wfTimestamp(TS_MW,$timestamp);
$this->mAuto = $auto;
- if( empty( $expiry ) ) {
- $this->mExpiry = $expiry;
- } else {
- $this->mExpiry = wfTimestamp( TS_MW, $expiry );
- }
+ $this->mAnonOnly = $anonOnly;
+ $this->mCreateAccount = $createAccount;
+ $this->mExpiry = self::decodeExpiry( $expiry );
$this->mForUpdate = false;
$this->mFromMaster = false;
$this->initialiseRange();
}
- /*static*/ function newFromDB( $address, $user = 0, $killExpired = true )
+ static function newFromDB( $address, $user = 0, $killExpired = true )
{
- $ban = new Block();
- $ban->load( $address, $user, $killExpired );
- return $ban;
+ $block = new Block();
+ $block->load( $address, $user, $killExpired );
+ if ( $block->isValid() ) {
+ return $block;
+ } else {
+ return null;
+ }
+ }
+
+ static function newFromID( $id )
+ {
+ $dbr =& wfGetDB( DB_SLAVE );
+ $res = $dbr->resultObject( $dbr->select( 'ipblocks', '*',
+ array( 'ipb_id' => $id ), __METHOD__ ) );
+ $block = new Block;
+ if ( $block->loadFromResult( $res ) ) {
+ return $block;
+ } else {
+ return null;
+ }
}
function clear()
{
$this->mAddress = $this->mReason = $this->mTimestamp = '';
- $this->mUser = $this->mBy = 0;
+ $this->mId = $this->mAnonOnly = $this->mCreateAccount =
+ $this->mAuto = $this->mUser = $this->mBy = 0;
$this->mByName = false;
-
}
/**
if ( $this->mForUpdate || $this->mFromMaster ) {
$db =& wfGetDB( DB_MASTER );
if ( !$this->mForUpdate || ($wgAntiLockFlags & ALF_NO_BLOCK_LOCK) ) {
- $options = '';
+ $options = array();
} else {
- $options = 'FOR UPDATE';
+ $options = array( 'FOR UPDATE' );
}
} else {
$db =& wfGetDB( DB_SLAVE );
- $options = '';
+ $options = array();
}
return $db;
}
/**
* Get a ban from the DB, with either the given address or the given username
+ *
+ * @param string $address The IP address of the user, or blank to skip IP blocks
+ * @param integer $user The user ID, or zero for anonymous users
+ * @param bool $killExpired Whether to delete expired rows while loading
+ *
*/
function load( $address = '', $user = 0, $killExpired = true )
{
- $fname = 'Block::load';
wfDebug( "Block::load: '$address', '$user', $killExpired\n" );
- $options = '';
+ $options = array();
$db =& $this->getDBOptions( $options );
$ret = false;
$killed = false;
- $ipblocks = $db->tableName( 'ipblocks' );
if ( 0 == $user && $address == '' ) {
# Invalid user specification, not blocked
$this->clear();
return false;
- } elseif ( $address == '' ) {
- $sql = "SELECT * FROM $ipblocks WHERE ipb_user={$user} $options";
- } elseif ( $user == '' ) {
- $sql = "SELECT * FROM $ipblocks WHERE ipb_address=" . $db->addQuotes( $address ) . " $options";
- } elseif ( $options == '' ) {
- # If there are no options (e.g. FOR UPDATE), use a UNION
- # so that the query can make efficient use of indices
- $sql = "SELECT * FROM $ipblocks WHERE ipb_address='" . $db->strencode( $address ) .
- "' UNION SELECT * FROM $ipblocks WHERE ipb_user={$user}";
- } else {
- # If there are options, a UNION can not be used, use one
- # SELECT instead. Will do a full table scan.
- $sql = "SELECT * FROM $ipblocks WHERE (ipb_address='" . $db->strencode( $address ) .
- "' OR ipb_user={$user}) $options";
}
- $res = $db->query( $sql, $fname );
- if ( 0 != $db->numRows( $res ) ) {
+ # Try user block
+ if ( $user ) {
+ $res = $db->resultObject( $db->select( 'ipblocks', '*', array( 'ipb_user' => $user ),
+ __METHOD__, $options ) );
+ if ( $this->loadFromResult( $res, $killExpired ) ) {
+ return true;
+ }
+ }
+
+ # Try IP block
+ if ( $address ) {
+ $conds = array( 'ipb_address' => $address );
+ if ( $user ) {
+ $conds['ipb_anon_only'] = 0;
+ }
+ $res = $db->resultObject( $db->select( 'ipblocks', '*', $conds, __METHOD__, $options ) );
+ if ( $this->loadFromResult( $res, $killExpired ) ) {
+ return true;
+ }
+ }
+
+ # Try range block
+ if ( $this->loadRange( $address, $killExpired, $user == 0 ) ) {
+ return true;
+ }
+
+ # Give up
+ $this->clear();
+ return false;
+ }
+
+ /**
+ * Fill in member variables from a result wrapper
+ */
+ function loadFromResult( ResultWrapper $res, $killExpired = true ) {
+ $ret = false;
+ if ( 0 != $res->numRows() ) {
# Get first block
- $row = $db->fetchObject( $res );
+ $row = $res->fetchObject();
$this->initFromRow( $row );
if ( $killExpired ) {
do {
$killed = $this->deleteIfExpired();
if ( $killed ) {
- $row = $db->fetchObject( $res );
+ $row = $res->fetchObject();
if ( $row ) {
$this->initFromRow( $row );
}
} while ( $killed && $row );
# If there were any left after the killing finished, return true
- if ( !$row ) {
- $ret = false;
- $this->clear();
- } else {
+ if ( $row ) {
$ret = true;
}
} else {
$ret = true;
}
}
- $db->freeResult( $res );
-
- # No blocks found yet? Try looking for range blocks
- if ( !$ret && $address != '' ) {
- $ret = $this->loadRange( $address, $killExpired );
- }
- if ( !$ret ) {
- $this->clear();
- }
-
+ $res->free();
return $ret;
}
* Search the database for any range blocks matching the given address, and
* load the row if one is found.
*/
- function loadRange( $address, $killExpired = true )
+ function loadRange( $address, $killExpired = true, $isAnon = true )
{
- $fname = 'Block::loadRange';
-
$iaddr = wfIP2Hex( $address );
if ( $iaddr === false ) {
# Invalid address
# Blocks should not cross a /16 boundary.
$range = substr( $iaddr, 0, 4 );
- $options = '';
+ $options = array();
$db =& $this->getDBOptions( $options );
- $ipblocks = $db->tableName( 'ipblocks' );
- $sql = "SELECT * FROM $ipblocks WHERE ipb_range_start LIKE '$range%' ".
- "AND ipb_range_start <= '$iaddr' AND ipb_range_end >= '$iaddr' $options";
- $res = $db->query( $sql, $fname );
- $row = $db->fetchObject( $res );
-
- $success = false;
- if ( $row ) {
- # Found a row, initialise this object
- $this->initFromRow( $row );
-
- # Is it expired?
- if ( !$killExpired || !$this->deleteIfExpired() ) {
- # No, return true
- $success = true;
- }
+ $conds = array(
+ "ipb_range_start LIKE '$range%'",
+ "ipb_range_start <= '$iaddr'",
+ "ipb_range_end >= '$iaddr'"
+ );
+ if ( !$isAnon ) {
+ $conds['ipb_anon_only'] = 0;
}
- $db->freeResult( $res );
+ $res = $db->resultObject( $db->select( 'ipblocks', '*', $conds, __METHOD__, $options ) );
+ $success = $this->loadFromResult( $res, $killExpired );
return $success;
}
$this->mUser = $row->ipb_user;
$this->mBy = $row->ipb_by;
$this->mAuto = $row->ipb_auto;
+ $this->mAnonOnly = $row->ipb_anon_only;
+ $this->mCreateAccount = $row->ipb_create_account;
$this->mId = $row->ipb_id;
- $this->mExpiry = $row->ipb_expiry ?
- wfTimestamp(TS_MW,$row->ipb_expiry) :
- $row->ipb_expiry;
+ $this->mExpiry = self::decodeExpiry( $row->ipb_expiry );
if ( isset( $row->user_name ) ) {
$this->mByName = $row->user_name;
} else {
function delete()
{
- $fname = 'Block::delete';
if (wfReadOnly()) {
- return;
+ return false;
}
- $dbw =& wfGetDB( DB_MASTER );
-
- if ( $this->mAddress == '' ) {
- $condition = array( 'ipb_id' => $this->mId );
- } else {
- $condition = array( 'ipb_address' => $this->mAddress );
+ if ( !$this->mId ) {
+ throw new MWException( "Block::delete() now requires that the mId member be filled\n" );
}
- return( $dbw->delete( 'ipblocks', $condition, $fname ) > 0 ? true : false );
+
+ $dbw =& wfGetDB( DB_MASTER );
+ $dbw->delete( 'ipblocks', array( 'ipb_id' => $this->mId ), __METHOD__ );
+ return $dbw->affectedRows() > 0;
}
function insert()
{
wfDebug( "Block::insert; timestamp {$this->mTimestamp}\n" );
$dbw =& wfGetDB( DB_MASTER );
+ $dbw->begin();
+
+ # Don't collide with expired blocks
+ Block::purgeExpired();
+
$ipb_id = $dbw->nextSequenceValue('ipblocks_ipb_id_val');
$dbw->insert( 'ipblocks',
array(
'ipb_reason' => $this->mReason,
'ipb_timestamp' => $dbw->timestamp($this->mTimestamp),
'ipb_auto' => $this->mAuto,
- 'ipb_expiry' => $this->mExpiry ?
- $dbw->timestamp($this->mExpiry) :
- $this->mExpiry,
+ 'ipb_anon_only' => $this->mAnonOnly,
+ 'ipb_create_account' => $this->mCreateAccount,
+ 'ipb_expiry' => self::encodeExpiry( $this->mExpiry, $dbw ),
'ipb_range_start' => $this->mRangeStart,
'ipb_range_end' => $this->mRangeEnd,
- ), 'Block::insert'
+ ), 'Block::insert', array( 'IGNORE' )
);
+ $affected = $dbw->affectedRows();
+ $dbw->commit();
+ return $affected;
}
function deleteIfExpired()
return wfSetVar( $this->mFromMaster, $x );
}
- /* static */ function getAutoblockExpiry( $timestamp )
+ function getRedactedName() {
+ if ( $this->mAuto ) {
+ return '#' . $this->mId;
+ } else {
+ return $this->mAddress;
+ }
+ }
+
+ /**
+ * Encode expiry for DB
+ */
+ static function encodeExpiry( $expiry, $db ) {
+ if ( $expiry == '' || $expiry == Block::infinity() ) {
+ return Block::infinity();
+ } else {
+ return $db->timestamp( $expiry );
+ }
+ }
+
+ /**
+ * Decode expiry which has come from the DB
+ */
+ static function decodeExpiry( $expiry ) {
+ if ( $expiry == '' || $expiry == Block::infinity() ) {
+ return Block::infinity();
+ } else {
+ return wfTimestamp( TS_MW, $expiry );
+ }
+ }
+
+ static function getAutoblockExpiry( $timestamp )
{
global $wgAutoblockExpiry;
return wfTimestamp( TS_MW, wfTimestamp( TS_UNIX, $timestamp ) + $wgAutoblockExpiry );
}
- /* static */ function normaliseRange( $range )
+ static function normaliseRange( $range )
{
$parts = explode( '/', $range );
if ( count( $parts ) == 2 ) {
return $range;
}
+ /**
+ * Purge expired blocks from the ipblocks table
+ */
+ static function purgeExpired() {
+ $dbw =& wfGetDB( DB_MASTER );
+ $dbw->delete( 'ipblocks', array( 'ipb_expiry < ' . $dbw->addQuotes( $dbw->timestamp() ) ), __METHOD__ );
+ }
+
+ static function infinity() {
+ # This is a special keyword for timestamps in PostgreSQL, and
+ # works with CHAR(14) as well because "i" sorts after all numbers.
+ return 'infinity';
+
+ /*
+ static $infinity;
+ if ( !isset( $infinity ) ) {
+ $dbr =& wfGetDB( DB_SLAVE );
+ $infinity = $dbr->bigTimestamp();
+ }
+ return $infinity;
+ */
+ }
+
}
?>
$this->BlockReason = $wgRequest->getText( 'wpBlockReason' );
$this->BlockExpiry = $wgRequest->getVal( 'wpBlockExpiry', wfMsg('ipbotheroption') );
$this->BlockOther = $wgRequest->getVal( 'wpBlockOther', '' );
+ $this->BlockAnonOnly = $wgRequest->getBool( 'wpAnonOnly' );
+
+ # Unchecked checkboxes are not included in the form data at all, so having one
+ # that is true by default is a bit tricky
+ if ( $wgRequest->wasPosted() ) {
+ $this->BlockCreateAccount = $wgRequest->getBool( 'wpCreateAccount', false );
+ } else {
+ $this->BlockCreateAccount = $wgRequest->getBool( 'wpCreateAccount', true );
+ }
}
function showForm( $err ) {
$mIpbothertime = wfMsgHtml( 'ipbotheroption' );
$mIpbreason = wfMsgHtml( 'ipbreason' );
$mIpbsubmit = wfMsgHtml( 'ipbsubmit' );
+ $mIpbanononly = wfMsgHtml( 'ipbanononly' );
+ $mIpbcreateaccount = wfMsgHtml( 'ipbcreateaccount' );
$titleObj = Title::makeTitle( NS_SPECIAL, 'Blockip' );
$action = $titleObj->escapeLocalURL( "action=submit" );
$scBlockReason = htmlspecialchars( $this->BlockReason );
$scBlockOtherTime = htmlspecialchars( $this->BlockOther );
$scBlockExpiryOptions = htmlspecialchars( wfMsgForContent( 'ipboptions' ) );
+ $anonOnlyChecked = $this->BlockAnonOnly ? 'checked' : '';
+ $createAccountChecked = $this->BlockCreateAccount ? 'checked' : '';
$showblockoptions = $scBlockExpiryOptions != '-';
if (!$showblockoptions)
<tr>
<td align=\"right\">{$mIpaddress}:</td>
<td align=\"left\">
- <input tabindex='1' type='text' size='20' name=\"wpBlockAddress\" value=\"{$scBlockAddress}\" />
+ <input tabindex='1' type='text' size='40' name=\"wpBlockAddress\" value=\"{$scBlockAddress}\" />
</td>
</tr>
<tr>");
<tr>
<td> </td>
<td align=\"left\">
+ <label>
+ <input type='checkbox' name='wpAnonOnly' value='1' $anonOnlyChecked />
+ {$mIpbanononly}
+ </label>
+ </td>
+ </tr>
+ <tr>
+ <td> </td>
+ <td align=\"left\">
+ <label>
+ <input type='checkbox' name='wpCreateAccount' value='1' $createAccountChecked />
+ {$mIpbcreateaccount}
+ </label>
+ </td>
+ </tr>
+ <tr>
+ <td style='padding-top: 1em'> </td>
+ <td style='padding-top: 1em' align=\"left\">
<input tabindex='4' type='submit' name=\"wpBlock\" value=\"{$mIpbsubmit}\" />
</td>
</tr>
}
if ( $expirestr == 'infinite' || $expirestr == 'indefinite' ) {
- $expiry = '';
+ $expiry = Block::infinity();
} else {
# Convert GNU-style date, on error returns -1 for PHP <5.1 and false for PHP >=5.1
$expiry = strtotime( $expirestr );
}
$expiry = wfTimestamp( TS_MW, $expiry );
-
}
# Create block
# Note: for a user block, ipb_address is only for display purposes
- $ban = new Block( $this->BlockAddress, $userId, $wgUser->getID(),
- $this->BlockReason, wfTimestampNow(), 0, $expiry );
+ $block = new Block( $this->BlockAddress, $userId, $wgUser->getID(),
+ $this->BlockReason, wfTimestampNow(), 0, $expiry, $this->BlockAnonOnly,
+ $this->BlockCreateAccount );
- if (wfRunHooks('BlockIp', array(&$ban, &$wgUser))) {
+ if (wfRunHooks('BlockIp', array(&$block, &$wgUser))) {
- $ban->insert();
+ if ( !$block->insert() ) {
+ $this->showForm( wfMsg( 'ipb_already_blocked',
+ htmlspecialchars( $this->BlockAddress ) ) );
+ return;
+ }
- wfRunHooks('BlockIpComplete', array($ban, $wgUser));
+ wfRunHooks('BlockIpComplete', array($block, $wgUser));
# Make log entry
$log = new LogPage( 'block' );
global $wgUser, $wgOut, $wgRequest;
$ip = $wgRequest->getVal( 'wpUnblockAddress', $wgRequest->getVal( 'ip' ) );
+ $id = $wgRequest->getVal( 'id' );
$reason = $wgRequest->getText( 'wpUnblockReason' );
$action = $wgRequest->getText( 'action' );
+ $successip = $wgRequest->getVal( 'successip' );
- $ipu = new IPUnblockForm( $ip, $reason );
+ $ipu = new IPUnblockForm( $ip, $id, $reason );
if ( "success" == $action ) {
- $ipu->showList( $wgOut->parse( wfMsg( 'unblocked', $ip ) ) );
+ $ipu->showList( $wgOut->parse( wfMsg( 'unblocked', $successip ) ) );
} else if ( "submit" == $action && $wgRequest->wasPosted() &&
$wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ) ) ) {
if ( ! $wgUser->isAllowed('block') ) {
* @subpackage SpecialPage
*/
class IPUnblockForm {
- var $ip, $reason;
+ var $ip, $reason, $id;
- function IPUnblockForm( $ip, $reason ) {
+ function IPUnblockForm( $ip, $id, $reason ) {
$this->ip = $ip;
+ $this->id = $id;
$this->reason = $reason;
}
}
$token = htmlspecialchars( $wgUser->editToken() );
+ $addressPart = false;
+ if ( $this->id ) {
+ $block = Block::newFromID( $this->id );
+ if ( $block ) {
+ $encName = htmlspecialchars( $block->getRedactedName() );
+ $encId = htmlspecialchars( $this->id );
+ $addressPart = $encName . "<input type='hidden' name=\"id\" value=\"$encId\" />";
+ }
+ }
+ if ( !$addressPart ) {
+ $addressPart = "<input tabindex='1' type='text' size='20' " .
+ "name=\"wpUnblockAddress\" value=\"" . htmlspecialchars( $this->ip ) . "\" />";
+ }
+
$wgOut->addHTML( "
<form id=\"unblockip\" method=\"post\" action=\"{$action}\">
<table border='0'>
<tr>
<td align='right'>{$ipa}:</td>
<td align='left'>
- <input tabindex='1' type='text' size='20' name=\"wpUnblockAddress\" value=\"" . htmlspecialchars( $this->ip ) . "\" />
+ {$addressPart}
</td>
</tr>
<tr>
function doSubmit() {
global $wgOut;
- $block = new Block();
- $this->ip = trim( $this->ip );
-
- if ( $this->ip{0} == "#" ) {
- $block->mId = substr( $this->ip, 1 );
+ if ( $this->id ) {
+ $block = Block::newFromID( $this->id );
+ if ( $block ) {
+ $this->ip = $block->getRedactedName();
+ }
} else {
- $block->mAddress = $this->ip;
+ $block = new Block();
+ $this->ip = trim( $this->ip );
+ if ( substr( $this->ip, 0, 1 ) == "#" ) {
+ $id = substr( $this->ip, 1 );
+ $block = Block::newFromID( $id );
+ } else {
+ $block = Block::newFromDB( $this->ip );
+ if ( !$block ) {
+ $block = null;
+ }
+ }
+ }
+ $success = false;
+ if ( $block ) {
+ # Delete block
+ if ( $block->delete() ) {
+ # Make log entry
+ $log = new LogPage( 'block' );
+ $log->addEntry( 'unblock', Title::makeTitle( NS_USER, $this->ip ), $this->reason );
+ $success = true;
+ }
}
- # Delete block (if it exists)
- # We should probably check for errors rather than just declaring success
- $block->delete();
-
- # Make log entry
- $log = new LogPage( 'block' );
- $log->addEntry( 'unblock', Title::makeTitle( NS_USER, $this->ip ), $this->reason );
-
- # Report to the user
- $titleObj = Title::makeTitle( NS_SPECIAL, "Ipblocklist" );
- $success = $titleObj->getFullURL( "action=success&ip=" . urlencode( $this->ip ) );
- $wgOut->redirect( $success );
+ if ( $success ) {
+ # Report to the user
+ $titleObj = Title::makeTitle( NS_SPECIAL, "Ipblocklist" );
+ $success = $titleObj->getFullURL( "action=success&successip=" . urlencode( $this->ip ) );
+ $wgOut->redirect( $success );
+ } else {
+ if ( !$this->ip && $this->id ) {
+ $this->ip = '#' . $this->id;
+ }
+ $this->showForm( wfMsg( 'ipb_cant_unblock', htmlspecialchars( $this->id ) ) );
+ }
}
function showList( $msg ) {
if ( "" != $msg ) {
$wgOut->setSubtitle( $msg );
}
- global $wgRequest;
- list( $this->limit, $this->offset ) = $wgRequest->getLimitOffset();
- $this->counter = 0;
- $paging = '<p>' . wfViewPrevNext( $this->offset, $this->limit,
- Title::makeTitle( NS_SPECIAL, 'Ipblocklist' ),
- 'ip=' . urlencode( $this->ip ) ) . "</p>\n";
- $wgOut->addHTML( $paging );
+ // Purge expired entries on one in every 10 queries
+ if ( !mt_rand( 0, 10 ) ) {
+ Block::purgeExpired();
+ }
- $search = $this->searchForm();
- $wgOut->addHTML( $search );
-
- $wgOut->addHTML( "<ul>" );
- if( !Block::enumBlocks( array( &$this, "addRow" ), 0 ) ) {
- // FIXME hack to solve #bug 1487
- $wgOut->addHTML( '<li>'.wfMsgHtml( 'ipblocklistempty' ).'</li>' );
+ $conds = array();
+ if ( $this->ip == '' ) {
+ // No extra conditions
+ } elseif ( substr( $this->ip, 0, 1 ) == '#' ) {
+ $conds['ipb_id'] = substr( $this->ip, 1 );
+ } elseif ( wfIP2Unsigned( $this->ip ) !== false ) {
+ $conds['ipb_address'] = $this->ip;
+ $conds['ipb_auto'] = 0;
+ } else {
+ $user = User::newFromName( $this->ip );
+ if ( ( $id = $user->getID() ) != 0 ) {
+ $conds['ipb_user'] = $id;
+ }
+ }
+
+ $pager = new IPBlocklistPager( $this, $conds );
+ $s = $pager->getNavigationBar() .
+ $this->searchForm();
+ if ( $pager->getNumRows() ) {
+ $s .= "<ul>" .
+ $pager->getBody() .
+ "</ul>";
+ } else {
+ $s .= '<p>' . wfMsgHTML( 'ipblocklistempty' ) . '</p>';
}
- $wgOut->addHTML( "</ul>\n" );
- $wgOut->addHTML( $paging );
+ $s .= $pager->getNavigationBar();
+ $wgOut->addHTML( $s );
}
function searchForm() {
- global $wgTitle;
+ global $wgTitle, $wgRequest;
return
wfElement( 'form', array(
'action' => $wgTitle->getLocalUrl() ),
wfElement( 'input', array(
'type' => 'hidden',
'name' => 'limit',
- 'value' => $this->limit ) ).
+ 'value' => $wgRequest->getText( 'limit' ) ) ) .
wfElement( 'input', array(
'name' => 'ip',
'value' => $this->ip ) ) .
/**
* Callback function to output a block
*/
- function addRow( $block, $tag ) {
- global $wgOut, $wgUser, $wgLang;
-
- if( $this->ip != '' ) {
- if( $block->mAuto ) {
- if( stristr( $block->mId, $this->ip ) == false ) {
- return;
- }
- } else {
- if( stristr( $block->mAddress, $this->ip ) == false ) {
- return;
- }
- }
- }
+ function formatRow( $block ) {
+ global $wgUser, $wgLang;
- // Loading blocks is fast; displaying them is slow.
- // Quick hack for paging.
- $this->counter++;
- if( $this->counter <= $this->offset ) {
- return;
- }
- if( $this->counter - $this->offset > $this->limit ) {
- return;
- }
-
- $fname = 'IPUnblockForm-addRow';
- wfProfileIn( $fname );
+ wfProfileIn( __METHOD__ );
static $sk=null, $msg=null;
$sk = $wgUser->getSkin();
if( is_null( $msg ) ) {
$msg = array();
- foreach( array( 'infiniteblock', 'expiringblock', 'contribslink', 'unblocklink' ) as $key ) {
+ $keys = array( 'infiniteblock', 'expiringblock', 'contribslink', 'unblocklink',
+ 'anononlyblock', 'createaccountblock' );
+ foreach( $keys as $key ) {
$msg[$key] = wfMsgHtml( $key );
}
$msg['blocklistline'] = wfMsg( 'blocklistline' );
$msg['contribslink'] = wfMsg( 'contribslink' );
}
-
# Prepare links to the blocker's user and talk pages
$blocker_name = $block->getByName();
$blocker = $sk->MakeLinkObj( Title::makeTitle( NS_USER, $blocker_name ), $blocker_name );
# Prepare links to the block target's user and contribs. pages (as applicable, don't do it for autoblocks)
if( $block->mAuto ) {
- $target = '#' . $block->mId; # Hide the IP addresses of auto-blocks; privacy
+ $target = $block->getRedactedName(); # Hide the IP addresses of auto-blocks; privacy
} else {
$target = $sk->makeLinkObj( Title::makeTitle( NS_USER, $block->mAddress ), $block->mAddress );
$target .= ' (' . $sk->makeKnownLinkObj( Title::makeTitle( NS_SPECIAL, 'Contributions' ), $msg['contribslink'], 'target=' . urlencode( $block->mAddress ) ) . ')';
}
- # Prep the address for the unblock link, masking autoblocks as before
- $addr = $block->mAuto ? '#' . $block->mId : $block->mAddress;
-
$formattedTime = $wgLang->timeanddate( $block->mTimestamp, true );
- if ( $block->mExpiry === "" ) {
- $formattedExpiry = $msg['infiniteblock'];
+ $properties = array();
+ if ( $block->mExpiry === "" || $block->mExpiry === Block::infinity() ) {
+ $properties[] = $msg['infiniteblock'];
} else {
- $formattedExpiry = wfMsgReplaceArgs( $msg['expiringblock'],
+ $properties[] = wfMsgReplaceArgs( $msg['expiringblock'],
array( $wgLang->timeanddate( $block->mExpiry, true ) ) );
}
+ if ( $block->mAnonOnly ) {
+ $properties[] = $msg['anononlyblock'];
+ }
+ if ( $block->mCreateAccount ) {
+ $properties[] = $msg['createaccountblock'];
+ }
+ $properties = implode( ', ', $properties );
- $line = wfMsgReplaceArgs( $msg['blocklistline'], array( $formattedTime, $blocker, $target, $formattedExpiry ) );
+ $line = wfMsgReplaceArgs( $msg['blocklistline'], array( $formattedTime, $blocker, $target, $properties ) );
- $wgOut->addHTML( "<li>{$line}" );
+ $s = "<li>{$line}";
if ( $wgUser->isAllowed('block') ) {
$titleObj = Title::makeTitle( NS_SPECIAL, "Ipblocklist" );
- $wgOut->addHTML( ' (' . $sk->makeKnownLinkObj($titleObj, $msg['unblocklink'], 'action=unblock&ip=' . urlencode( $addr ) ) . ')' );
+ $s .= ' (' . $sk->makeKnownLinkObj($titleObj, $msg['unblocklink'], 'action=unblock&id=' . urlencode( $block->mId ) ) . ')';
}
- $wgOut->addHTML( $sk->commentBlock( $block->mReason ) );
- $wgOut->addHTML( "</li>\n" );
- wfProfileOut( $fname );
+ $s .= $sk->commentBlock( $block->mReason );
+ $s .= "</li>\n";
+ wfProfileOut( __METHOD__ );
+ return $s;
+ }
+}
+
+class IPBlocklistPager extends ReverseChronologicalPager {
+ public $mForm, $mConds;
+
+ function __construct( $form, $conds = array() ) {
+ $this->mForm = $form;
+ $this->mConds = $conds;
+ parent::__construct();
+ }
+
+ function getStartBody() {
+ wfProfileIn( __METHOD__ );
+ # Do a link batch query
+ $this->mResult->seek( 0 );
+ $lb = new LinkBatch;
+
+ /*
+ while ( $row = $this->mResult->fetchObject() ) {
+ $lb->addObj( Title::makeTitleSafe( NS_USER, $row->user_name ) );
+ $lb->addObj( Title::makeTitleSafe( NS_USER_TALK, $row->user_name ) );
+ $lb->addObj( Title::makeTitleSafe( NS_USER, $row->ipb_address ) );
+ $lb->addObj( Title::makeTitleSafe( NS_USER_TALK, $row->ipb_address ) );
+ }*/
+ # Faster way
+ # Usernames and titles are in fact related by a simple substitution of space -> underscore
+ # The last few lines of Title::secureAndSplit() tell the story.
+ while ( $row = $this->mResult->fetchObject() ) {
+ $name = str_replace( ' ', '_', $row->user_name );
+ $lb->add( NS_USER, $name );
+ $lb->add( NS_USER_TALK, $name );
+ $name = str_replace( ' ', '_', $row->ipb_address );
+ $lb->add( NS_USER, $name );
+ $lb->add( NS_USER_TALK, $name );
+ }
+ $lb->execute();
+ wfProfileOut( __METHOD__ );
+ return '';
+ }
+
+ function formatRow( $row ) {
+ $block = new Block;
+ $block->initFromRow( $row );
+ return $this->mForm->formatRow( $block );
+ }
+
+ function getQueryInfo() {
+ $conds = $this->mConds;
+ $conds[] = 'ipb_expiry>' . $this->mDb->addQuotes( $this->mDb->timestamp() );
+ $conds[] = 'ipb_by=user_id';
+ return array(
+ 'tables' => array( 'ipblocks', 'user' ),
+ 'fields' => 'ipblocks.*,user_name',
+ 'conds' => $conds,
+ );
+ }
+
+ function getIndexField() {
+ return 'ipb_timestamp';
}
}
$wgOut->returnToMain( false );
}
+ /** */
+ function userBlockedMessage() {
+ global $wgOut;
+
+ # Let's be nice about this, it's likely that this feature will be used
+ # for blocking large numbers of innocent people, e.g. range blocks on
+ # schools. Don't blame it on the user. There's a small chance that it
+ # really is the user's fault, i.e. the username is blocked and they
+ # haven't bothered to log out before trying to create an account to
+ # evade it, but we'll leave that to their guilty conscience to figure
+ # out.
+
+ $wgOut->setPageTitle( wfMsg( 'cantcreateaccounttitle' ) );
+ $wgOut->setRobotpolicy( 'noindex,nofollow' );
+ $wgOut->setArticleRelated( false );
+
+ $ip = wfGetIP();
+ $wgOut->addWikiText( wfMsg( 'cantcreateaccounttext', $ip ) );
+ $wgOut->returnToMain( false );
+ }
+
/**
* @private
*/
global $wgUser, $wgOut, $wgAllowRealName, $wgEnableEmail;
global $wgCookiePrefix, $wgAuth, $wgLoginLanguageSelector;
- if ( $this->mType == 'signup' && !$wgUser->isAllowedToCreateAccount() ) {
- $this->userNotPrivilegedMessage();
- return;
+ if ( $this->mType == 'signup' ) {
+ if ( !$wgUser->isAllowed( 'createaccount' ) ) {
+ $this->userNotPrivilegedMessage();
+ return;
+ } elseif ( $wgUser->isBlockedFromCreateAccount() ) {
+ $this->userBlockedMessage();
+ return;
+ }
}
if ( '' == $this->mName ) {
function showCreateOrLoginLink( &$user ) {
if( $this->mType == 'signup' ) {
return( true );
- } elseif( $user->isAllowedToCreateAccount() ) {
+ } elseif( $user->isAllowed( 'createaccount' ) ) {
return( true );
} else {
return( false );
*/
var $mBlockedby; //!<
var $mBlockreason; //!<
+ var $mBlock; //!<
var $mDataLoaded; //!<
var $mEmail; //!<
var $mEmailAuthenticated; //!<
*/
function __sleep() {
return array(
-'mBlockedby',
-'mBlockreason',
'mDataLoaded',
'mEmail',
'mEmailAuthenticated',
$ip = wfGetIP();
# User/IP blocking
- $block = new Block();
- $block->fromMaster( !$bFromSlave );
- if ( $block->load( $ip , $this->mId ) ) {
+ $this->mBlock = new Block();
+ $this->mBlock->fromMaster( !$bFromSlave );
+ if ( $this->mBlock->load( $ip , $this->mId ) ) {
wfDebug( "$fname: Found block.\n" );
- $this->mBlockedby = $block->mBy;
- $this->mBlockreason = $block->mReason;
+ $this->mBlockedby = $this->mBlock->mBy;
+ $this->mBlockreason = $this->mBlock->mReason;
if ( $this->isLoggedIn() ) {
$this->spreadBlock();
}
} else {
+ $this->mBlock = null;
wfDebug( "$fname: No block.\n" );
}
$user->loadFromDatabase();
} else {
wfDebug( "User::loadFromSession() got from cache!\n" );
+ # Set block status to unloaded, that should be loaded every time
+ $user->mBlockedby = -1;
}
if ( isset( $_SESSION['wsToken'] ) ) {
}
$userblock = Block::newFromDB( '', $this->mId );
- if ( !$userblock->isValid() ) {
+ if ( !$userblock ) {
return;
}
# Check if this IP address is already blocked
$ipblock = Block::newFromDB( wfGetIP() );
- if ( $ipblock->isValid() ) {
+ if ( $ipblock ) {
# If the user is already blocked. Then check if the autoblock would
# excede the user block. If it would excede, then do nothing, else
# prolong block time
return $confstr;
}
+ function isBlockedFromCreateAccount() {
+ $this->getBlockedStatus();
+ return $this->mBlock && $this->mBlock->mCreateAccount;
+ }
+
function isAllowedToCreateAccount() {
- return $this->isAllowed( 'createaccount' ) && !$this->isBlocked();
+ return $this->isAllowed( 'createaccount' ) && !$this->isBlockedFromCreateAccount();
}
/**
'nocreatetitle' => 'Page creation limited',
'nocreatetext' => 'This site has restricted the ability to create new pages.
You can go back and edit an existing page, or [[Special:Userlogin|log in or create an account]].',
+'cantcreateaccounttitle' => 'Can\'t create account',
+'cantcreateaccounttext' => 'Account creation from this IP address (<b>$1</b>) has been blocked.
+This is probably due to persistent vandalism from your school or Internet service
+provider. ',
# History pages
#
'ipadressorusername' => 'IP Address or username',
'ipbexpiry' => 'Expiry',
'ipbreason' => 'Reason',
+'ipbanononly' => 'Block anonymous users only',
+'ipbcreateaccount' => 'Prevent account creation',
'ipbsubmit' => 'Block this user',
'ipbother' => 'Other time',
'ipboptions' => '2 hours:2 hours,1 day:1 day,3 days:3 days,1 week:1 week,2 weeks:2 weeks,1 month:1 month,3 months:3 months,6 months:6 months,1 year:1 year,infinite:infinite',
'blocklistline' => "$1, $2 blocked $3 ($4)",
'infiniteblock' => 'infinite',
'expiringblock' => 'expires $1',
+'anononlyblock' => 'anon. only',
+'createaccountblock' => 'account creation blocked',
'ipblocklistempty' => 'The blocklist is empty.',
'blocklink' => 'block',
'unblocklink' => 'unblock',
'unblocklogentry' => 'unblocked $1',
'range_block_disabled' => 'The sysop ability to create range blocks is disabled.',
'ipb_expiry_invalid' => 'Expiry time invalid.',
+'ipb_already_blocked' => '"$1" is already blocked',
'ip_range_invalid' => 'Invalid IP range.',
'proxyblocker' => 'Proxy blocker',
+'ipb_cant_unblock' => 'Error: Block ID $1 not found. It may have been unblocked already.',
'proxyblockreason' => 'Your IP address has been blocked because it is an open proxy. Please contact your Internet service provider or tech support and inform them of this serious security problem.',
'proxyblocksuccess' => 'Done.',
'sorbs' => 'SORBS DNSBL',
--- /dev/null
+-- Add extra option fields to the ipblocks table, add some extra indexes,
+-- convert infinity values in ipb_expiry to something that sorts better,
+-- extend ipb_address and range fields, add a unique index for block conflict
+-- detection.
+
+-- Conflicts in the new unique index can be handled by creating a new
+-- table and inserting into it instead of doing an ALTER TABLE.
+
+
+DROP TABLE IF EXISTS /*$wgDBprefix*/ipblocks_newunique;
+
+CREATE TABLE /*$wgDBprefix*/ipblocks_newunique (
+ ipb_id int(8) NOT NULL auto_increment,
+ ipb_address tinyblob NOT NULL default '',
+ ipb_user int(8) unsigned NOT NULL default '0',
+ ipb_by int(8) unsigned NOT NULL default '0',
+ ipb_reason tinyblob NOT NULL default '',
+ ipb_timestamp char(14) binary NOT NULL default '',
+ ipb_auto boolean NOT NULL default 0,
+ ipb_anon_only boolean NOT NULL default 0,
+ ipb_create_account boolean NOT NULL default 1,
+ ipb_expiry char(14) binary NOT NULL default '',
+ ipb_range_start tinyblob NOT NULL default '',
+ ipb_range_end tinyblob NOT NULL default '',
+
+ PRIMARY KEY ipb_id (ipb_id),
+ UNIQUE INDEX ipb_address_unique (ipb_address(255), ipb_user, ipb_auto),
+ INDEX ipb_user (ipb_user),
+ INDEX ipb_range (ipb_range_start(8), ipb_range_end(8)),
+ INDEX ipb_timestamp (ipb_timestamp),
+ INDEX ipb_expiry (ipb_expiry)
+
+) TYPE=InnoDB;
+
+INSERT IGNORE INTO /*$wgDBprefix*/ipblocks_newunique
+ (ipb_id, ipb_address, ipb_user, ipb_by, ipb_reason, ipb_timestamp, ipb_auto, ipb_expiry, ipb_range_start, ipb_range_end)
+ SELECT ipb_id, ipb_address, ipb_user, ipb_by, ipb_reason, ipb_timestamp, ipb_auto, ipb_expiry, ipb_range_start, ipb_range_end
+ FROM /*$wgDBprefix*/ipblocks;
+
+DROP TABLE IF EXISTS /*$wgDBprefix*/ipblocks_old;
+RENAME TABLE /*$wgDBprefix*/ipblocks TO /*$wgDBprefix*/ipblocks_old;
+RENAME TABLE /*$wgDBprefix*/ipblocks_newunique TO /*$wgDBprefix*/ipblocks;
+
-- Indicates that the IP address was banned because a banned
-- user accessed a page through it. If this is 1, ipb_address
-- will be hidden, and the block identified by block ID number.
- ipb_auto tinyint(1) NOT NULL default '0',
+ ipb_auto boolean NOT NULL default '0',
+ -- If set to 1, block applies only to logged-out users
+ ipb_anon_only boolean NOT NULL default 0,
+
+ -- Block prevents account creation from matching IP addresses
+ ipb_create_account boolean NOT NULL default 1,
+
-- Time at which the block will expire.
ipb_expiry char(14) binary NOT NULL default '',
ipb_range_end varchar(32) NOT NULL default '',
PRIMARY KEY ipb_id (ipb_id),
- INDEX ipb_address (ipb_address),
+
+ -- Unique index to support "user already blocked" messages
+ -- Any new options which prevent collisions should be included
+ UNIQUE INDEX ipb_address (ipb_address(255), ipb_user, ipb_auto, ipb_anon_only),
+
INDEX ipb_user (ipb_user),
- INDEX ipb_range (ipb_range_start(8), ipb_range_end(8))
+ INDEX ipb_range (ipb_range_start(8), ipb_range_end(8)),
+ INDEX ipb_timestamp (ipb_timestamp),
+ INDEX ipb_expiry (ipb_expiry)
) TYPE=InnoDB, DEFAULT CHARSET=utf8;
UNIQUE KEY ( qci_type )
-) TYPE=InnoDB;
\ No newline at end of file
+) TYPE=InnoDB;
ipb_id int(8) NOT NULL auto_increment,
-- Blocked IP address in dotted-quad form or user name.
- ipb_address varchar(40) binary NOT NULL default '',
+ ipb_address tinyblob NOT NULL default '',
-- Blocked user ID or 0 for IP blocks.
ipb_user int(8) unsigned NOT NULL default '0',
-- Indicates that the IP address was banned because a banned
-- user accessed a page through it. If this is 1, ipb_address
-- will be hidden, and the block identified by block ID number.
- ipb_auto tinyint(1) NOT NULL default '0',
+ ipb_auto boolean NOT NULL default 0,
+
+ -- If set to 1, block applies only to logged-out users
+ ipb_anon_only boolean NOT NULL default 0,
+
+ -- Block prevents account creation from matching IP addresses
+ ipb_create_account boolean NOT NULL default 1,
-- Time at which the block will expire.
ipb_expiry char(14) binary NOT NULL default '',
-- Start and end of an address range, in hexadecimal
-- Size chosen to allow IPv6
- ipb_range_start varchar(32) NOT NULL default '',
- ipb_range_end varchar(32) NOT NULL default '',
+ ipb_range_start tinyblob NOT NULL default '',
+ ipb_range_end tinyblob NOT NULL default '',
PRIMARY KEY ipb_id (ipb_id),
- INDEX ipb_address (ipb_address),
+
+ -- Unique index to support "user already blocked" messages
+ -- Any new options which prevent collisions should be included
+ UNIQUE INDEX ipb_address (ipb_address(255), ipb_user, ipb_auto, ipb_anon_only),
+
INDEX ipb_user (ipb_user),
- INDEX ipb_range (ipb_range_start(8), ipb_range_end(8))
+ INDEX ipb_range (ipb_range_start(8), ipb_range_end(8)),
+ INDEX ipb_timestamp (ipb_timestamp),
+ INDEX ipb_expiry (ipb_expiry)
) TYPE=InnoDB;
-- Cache of interwiki transclusion
--
CREATE TABLE /*$wgDBprefix*/transcache (
- tc_url VARCHAR(255) NOT NULL,
- tc_contents TEXT,
- tc_time INT NOT NULL,
- UNIQUE INDEX tc_url_idx(tc_url)
+ tc_url VARCHAR(255) NOT NULL,
+ tc_contents TEXT,
+ tc_time INT NOT NULL,
+ UNIQUE INDEX tc_url_idx(tc_url)
) TYPE=InnoDB;
CREATE TABLE /*$wgDBprefix*/logging (
) TYPE=InnoDB;
CREATE TABLE /*$wgDBprefix*/trackbacks (
- tb_id integer AUTO_INCREMENT PRIMARY KEY,
- tb_page integer REFERENCES page(page_id) ON DELETE CASCADE,
- tb_title varchar(255) NOT NULL,
- tb_url varchar(255) NOT NULL,
- tb_ex text,
- tb_name varchar(255),
-
- INDEX (tb_page)
+ tb_id integer AUTO_INCREMENT PRIMARY KEY,
+ tb_page integer REFERENCES page(page_id) ON DELETE CASCADE,
+ tb_title varchar(255) NOT NULL,
+ tb_url varchar(255) NOT NULL,
+ tb_ex text,
+ tb_name varchar(255),
+
+ INDEX (tb_page)
) TYPE=InnoDB;
-- Details of updates to cached special pages
CREATE TABLE /*$wgDBprefix*/querycache_info (
- -- Special page name
- -- Corresponds to a qc_type value
- qci_type varchar(32) NOT NULL default '',
+ -- Special page name
+ -- Corresponds to a qc_type value
+ qci_type varchar(32) NOT NULL default '',
- -- Timestamp of last update
- qci_timestamp char(14) NOT NULL default '19700101000000',
+ -- Timestamp of last update
+ qci_timestamp char(14) NOT NULL default '19700101000000',
- UNIQUE KEY ( qci_type )
+UNIQUE KEY ( qci_type )
) TYPE=InnoDB;
+
+-- vim: sw=2 sts=2 et
array( 'interwiki', 'iw_trans', 'patch-interwiki-trans.sql' ),
array( 'ipblocks', 'ipb_range_start', 'patch-ipb_range_start.sql' ),
array( 'site_stats', 'ss_images', 'patch-ss_images.sql' ),
+ array( 'ipblocks', 'ipb_anon_only', 'patch-ipb_anon_only.sql' ),
);
function rename_table( $from, $to, $patch ) {